package sdk3.components.TextInputExtended
{
	import sdk3.c_ARRAY;
	import sdk3.c_MATH;
	import sdk3.c_OBJECT;
	import sdk3.c_STRING;
	import sdk3.c_STRINGS_AND_CONSTANTS;
	import sdk3.c_VALIDATORS;
	
	import sdk3.components.TextInputExtended.AutoCompleteDropDown;
	
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.events.FocusEvent;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.geom.Point;
	import flash.ui.Keyboard;
	import flash.utils.Timer;
	
	import mx.automation.delegates.controls.ToolTipAutomationImpl;
	import mx.charts.series.ColumnSet;
	import mx.containers.VBox;
	import mx.controls.Alert;
	import mx.controls.DataGrid;
	import mx.controls.DateField;
	import mx.controls.TextInput;
	import mx.controls.ToolTip;
	import mx.controls.dataGridClasses.DataGridColumn;
	import mx.core.IToolTip;
	import mx.core.UIComponent;
	import mx.events.ToolTipEvent;
	import mx.managers.IFocusManagerComponent;
	import mx.managers.ISystemManager;
	import mx.managers.SystemManager;

	 /**
	 * Software: TextInputExtended (TIE)												
	 * Version:  1.7
	 * Copyright (C)
	 * Date:     2009_09_25_TIME_10:23:31 - 2010_08_16_TIME_22:39:57
	 * Author:    
	  * LICENSE:  GNU General Public License (http://www.gnu.org/licenses/gpl.html) 	 *  	 * English: 		This program is free software; 		you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; 		either version 3 of the License, or (at your option) any later version.   		This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 		without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 		See the GNU General Public License for more details. 		You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>.   		 * Excerpt English: 		 * Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), 		 * that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things   		Auf Deutsch: 		Dieses Programm ist freie Software. Sie können es unter den Bedingungen der GNU General Public License, wie von der Free Software Foundation veröffentlicht, 		weitergeben und/oder modifizieren, entweder gemäß Version 3 der Lizenz oder (nach Ihrer Option) jeder späteren Version. 		Die Veröffentlichung dieses Programms erfolgt in der Hoffnung, daß es Ihnen von Nutzen sein wird, aber OHNE IRGENDEINE GARANTIE, 		sogar ohne die implizite Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. 		Details finden Sie in der GNU General Public License. 		Sie sollten ein Exemplar der GNU General Public License zusammen mit diesem Programm erhalten haben. 		Falls nicht, siehe <http://www.gnu.org/licenses/>.   		 * Auszug Deutsch: 		 * Wenn wir von freier Software sprechen, so beziehen wir uns auf Freiheit, nicht auf den Preis. 		 * Unsere Allgemeinen Öffentlichen Lizenzen sind darauf angelegt, sicherzustellen, daß Sie die Freiheit haben, 		 * Kopien freier Software zu verbreiten (und dafür etwas zu berechnen, wenn Sie möchten), die Möglichkeit, 		 * daß Sie die Software als Quelltext erhalten oder den Quelltext auf Wunsch bekommen, daß Sie die Software 		 * ändern oder Teile davon in neuen freien Programmen verwenden dürfen und daß Sie wissen, daß Sie dies alles tun dürfen.   GNU General Public License (http://www.gnu.org/licenses/gpl.html)
	 * 
	 * English:
		This program is free software;
		you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation;
		either version 3 of the License, or (at your option) any later version.

 		This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
		without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
		See the GNU General Public License for more details.
		You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>.

 		 * Excerpt English:
		 * Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish),
		 * that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things

 		Auf Deutsch:
		Dieses Programm ist freie Software. Sie können es unter den Bedingungen der GNU General Public License, wie von der Free Software Foundation veröffentlicht,
		weitergeben und/oder modifizieren, entweder gemäß Version 3 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
		Die Veröffentlichung dieses Programms erfolgt in der Hoffnung, daß es Ihnen von Nutzen sein wird, aber OHNE IRGENDEINE GARANTIE,
		sogar ohne die implizite Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK.
		Details finden Sie in der GNU General Public License.
		Sie sollten ein Exemplar der GNU General Public License zusammen mit diesem Programm erhalten haben.
		Falls nicht, siehe <http://www.gnu.org/licenses/>.

 		 * Auszug Deutsch:
		 * Wenn wir von freier Software sprechen, so beziehen wir uns auf Freiheit, nicht auf den Preis.
		 * Unsere Allgemeinen Öffentlichen Lizenzen sind darauf angelegt, sicherzustellen, daß Sie die Freiheit haben,
		 * Kopien freier Software zu verbreiten (und dafür etwas zu berechnen, wenn Sie möchten), die Möglichkeit,
		 * daß Sie die Software als Quelltext erhalten oder den Quelltext auf Wunsch bekommen, daß Sie die Software
		 * ändern oder Teile davon in neuen freien Programmen verwenden dürfen und daß Sie wissen, daß Sie dies alles tun dürfen.
		
	 * PURPOSE:
	 * Extends the TextInput control.
	 * and adds additional functionality such as:
	 * 1. possibility to easily define what content is valid and what not by simply setting '_mode' to:
	 * 'everything' empty array means = everything allowed
	 * 'currency' -> only numeric values with a precision of 2 are allowed. (otherwise empty textinput)
	 * 'percent' -> Percentage minimum: 0.00 maximum 100.00
	 * 'int' -> only full numbers values with a precision of 0 are allowed. (otherwise empty textinput)
	 * 'abc' -> only non numeric text is allowed.
	 * 'date' -> only Date's are allowed. Format DD.MM.YYYY
	 * 'float' -> only Date's are allowed. Format DD.MM.YYYY
	 * 'alphanumeric' -> only alphanumeric Values allowed
	 * 'values' -> only the given individual values (as array to TIE._valid_values) are allowed/valid values
	 
	 * 2. easily check if a group of TIE is in-valid -> maybe not let the user save a dialog.
	 * if(TIE1.valid && TIE2.valid && TIE3.valid)
	 * { // everything okay proceed }
	 * else
	 * {
	 * 3. perform defined behaviours if invalid.
	 * 
	 * TIE1.behave('Datum ist ungültig',['delete content','show alert']);
	 * TIE2.behave('Periode ist ungültig'['delete content','show alert']);
	 * TIE3.behave('Irgendwas ist ungültig'['delete content','show alert']);
	 * } -> outputs 
	 * 
	 * 
	 * select_all_text_on_focusIn = true (default)
	 * 
	 * FEATURES:
	 * o improved currency reformat & validation.
	 *   
	 * TODO: XXX:
	 * o implement history function, on focus out the content is saved... when changed... it should be possible to select an value of the history via up/down arrows. 
	 * 
	 * CHANGELOG:
	 * 2010_02_15_TIME_15:29:37
	 * o added properties manually_changed, changed
	 * 
	 * 2010_08_14_TIME_09:00:08
	 * o added autocomplete capabilities
	 * o adding focus next on enter capabilities
	 * 
	 * 2010_08_16_TIME_22:39:57  120min
	 * o correct autoComplete-Tooltip placement, works with layout="absolute" and should also work with layout="vertical"
	 * 
	 * 2010_08_25_TIME_09:21:35  20min
	 * make selectedItem more easily available
	 * some bugfixing concerning filtering list & autocompleteKeys
	 * 
	 * 2010_08_20_TIME_17:23:48  51min
	 * how to filter the list of possible values
	 * possible values:
	 * -> "match beginning" -> will check if the beginning chars of _valid_values will match
	 * -> "match end" -> will check if the end of any of the _valid_values will match
	 * -> "anywhere" -> will check if user-input is inside any of the _valid_values
	 * 
	 * 2010_08_25_TIME_11:25:56  30min
	 * FocusNextComponent FocusPreviousComponent
	 * 
	 * 2011_04_11_TIME_21:32:41
	 * not every information needs to be displayed in the autoCompleteDropDown box.
	 * autocompleteHideKeys="{['ID']}"
	 * 
	 * USAGE EXAMPLE: 
	 *
	 * 1. copy TIE to src/assets/
	 * 2. in one's mxml declare a new namespace cc (customComponent):
	 *  xmlns:cc="components.*"
	 * 
	 * so it looks like this:
	 * <?xml version="1.0" encoding="utf-8"?>
	 <mx:Application
	 xmlns:mx="http://www.adobe.com/2006/mxml"
	 xmlns:cc="components.*"
	 ...
	 * 3. use it like <assets:TIE ...
	 **/
	public class TextInputExtended extends TextInput
	{
		public function TextInputExtended()
		{
			super();
			this.addEventListener(FocusEvent.FOCUS_IN, FOCUS_IN);
			this.addEventListener(Event.CHANGE, CHANGE);
			this.addEventListener(FocusEvent.FOCUS_OUT, FOCUS_OUT);
			this.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
		}
		
		public var c_MATH_inst:c_MATH = new c_MATH();
		
		/** reacts if user changes content of an TIE via keyboard. **/
		public var manually_changed:Boolean = false;
		/** reacts on any change, either from program logic or user keyboard input. **/
		public var changed:Boolean = false;

		/** if the entered values should be autocompleted with a value that matches closest from _valid_values
		 * lookAhead is currently implemented in a way
		 * that it only autoComplements, if there is only one choice left to autocomplete
		 * **/
		private var _lookAhead:Boolean = false;
		public function set lookAhead(lookAhead_var:Boolean):void
		{
			this._lookAhead = lookAhead_var;
		}
		public function get lookAhead():Boolean
		{
			return this._lookAhead;
		}

		/** this function is called, when the selection/selectedItem of the autocomplete changed **/
		public var selectedItem_UpdatedHandler:Function = null;
		
		private var _autocomplete:Boolean = false;
		public function set autocomplete(autocomplete_var:Boolean):void
		{
			this._autocomplete = autocomplete_var;
			this.ACDDL = new AutoCompleteDropDown();
			this.ACDDL.addEventListener("selectionChanged", autocompleteSelectionChanged);
			if(autocomplete)
			{
				if(this.hasEventListener(ToolTipEvent.TOOL_TIP_CREATE) == false)
				{
					this.addEventListener(FocusEvent.FOCUS_IN, ShowAutoCompleteDropDown);
					this.addEventListener(FocusEvent.FOCUS_OUT, ShowAutoCompleteDropDown);
/*					this.addEventListener(ToolTipEvent.TOOL_TIP_CREATE, ShowAutoCompleteDropDown2);
					this.addEventListener(ToolTipEvent.TOOL_TIP_END, ShowAutoCompleteDropDown2);
					this.addEventListener(ToolTipEvent.TOOL_TIP_SHOW, ShowAutoCompleteDropDown2);
					this.addEventListener(ToolTipEvent.TOOL_TIP_SHOWN, ShowAutoCompleteDropDown2);
					this.addEventListener(ToolTipEvent.TOOL_TIP_START, ShowAutoCompleteDropDown2);
					this.addEventListener(ToolTipEvent.TOOL_TIP_END, ShowAutoCompleteDropDown2);
					this.addEventListener(ToolTipEvent.TOOL_TIP_HIDE, ShowAutoCompleteDropDown2);
*/				}
			}
		}

		public function get autocomplete():Boolean
		{
			return this._autocomplete;
		}
		
		/** an Array of KEY-Strings,
		 * determines what key should be used to access the values for filtering the list & autocompletion
		 * this.dataProvider[KEYS]->AutoCompleteValues
		 * **/ 
		public var autocompleteKeys:Array = null;

		/** the property of the element of the array _valid_values contains the valid value
		 * basically valid_values_property and autocompleteKeys are doing the same/similar tasks
		 * **/
		public var valid_values_property:String = '';
		
		public var selectedItem:Object = null;
		/** when pushing UP/DOWN arrow-keys... the autocomplete selection changes **/
		public function autocompleteSelectionChanged(event:Event = null):void
		{
			// make selectedItem more easily available
			if(c_OBJECT_inst.not_empty(ACDDL.selectedItem))
			{
				this.selectedItem = ACDDL.selectedItem;
				this.dispatchEvent(new Event("selectedItem_Updated"));
				if(selectedItem_UpdatedHandler != null)
				{
					selectedItem_UpdatedHandler();
				}
			}
			if(c_OBJECT_inst.empty(autocompleteKeys))
			{
				throw new Error(" TextInputExtended: this TIE is in autocomplete mode, but autocompleteKeys are not set. TIE does not know what key->values to use for autocompletion. Please correct!");
			}
			else
			{
				if(this.ACDDL.selectedItem != null)
				{
					var SELECTION_START:int = 0;
					
					var AutoCompleteValue:String = "";
					var NewValue:String = "";
					
					for each(var KEY:String in autocompleteKeys)
					{
						if(autocompleteKeys.length == 1)
						{
							AutoCompleteValue = this.ACDDL.selectedItem[KEY];
						}
						else
						{
							AutoCompleteValue = AutoCompleteValue + " " + this.ACDDL.selectedItem[KEY];
						}
					}
	
					if(this.text == "")
					{
						this.text = AutoCompleteValue;
						this.setSelection(SELECTION_START,this.text.length);
					}
					if(AutoCompleteValue != this.text)
					{
						if(AutoCompleteValue == null)
						{
							return;
						}
						var LENGTH:int = 0;
						// cut to equal length
						if(this.text.length > AutoCompleteValue.length)
						{
							this.text = this.text.substr(0,AutoCompleteValue.length);
							LENGTH = this.text.length;
						}
						else
						{
							LENGTH = AutoCompleteValue.length;
						}
						
						// compare how much identical chars are in stuff user entered vs. AutoCompleteValue
						for(var counter:int = 0;!(counter == LENGTH);counter++)
						{
							if(counter <= AutoCompleteValue.length)
							{
								if(AutoCompleteValue.charAt(counter) == this.text.charAt(counter))
								{
									SELECTION_START++;
								}
								else
								{
									
									this.text = c_STRING_inst.replaceAt(this.text,counter+1,AutoCompleteValue.charAt(counter));
								}
							}
						}
						this.setSelection(SELECTION_START,this.text.length);
					}
					else if(this.text == AutoCompleteValue)
					{
						this.setSelection(SELECTION_START,this.text.length);
					}
				}
				
			}
		}
		
		public function FOCUS_IN(event:Event = null):void
		{
			if(this.validate_on == 'FOCUS_IN')
			{
				try
				{
					this.validate();
				}
				catch(e:Error)
				{
					trace("TextInputExtended: "+e);
				}
			}
			
			if(select_all_text_on_focusIn)
			{
				this.setSelection(0,this.text.length);
			}
			else
			{
				this.setSelection(this.text.length,this.text.length);
			}
			if(this.autocomplete)
			{
				ShowAutoCompleteDropDown(new FocusEvent("FOCUS_IN")); // new ToolTipEvent("TOOL_TIP_SHOW")
			}
		}
		
		/** checks if the content is a valid in max/min float value
		 * and if not automatically sets to maximum value. 
		 * **/
		public function validate_max_min_numeric_value():Boolean
		{
			valid = true;
			if(this.max_value != 'undefined')
			{
				if(parseFloat(this.text) > parseFloat(this.max_value))
				{
					valid = false;
					this.behave(['set to max numeric value']);
				}
			}
			
			if(this.min_value != 'undefined')
			{
				if(parseFloat(this.text) < parseFloat(this.min_value))
				{
					valid = false;
					this.behave(['set to min numeric value']);
				}
			}
			
			if(valid)
			{
				this.behave();
			}
			
			return valid;
		}
		
		/** checks if the content is a valid currency value in format 0.00 
		 * **/
		public var rounding:Boolean = false;
		public var last_entered_currency:String = '';
		public var reformat_currency_sign:Boolean = true;
		public function reformat_currency():void
		{
			/** lass nur nummern und . durch **/
			this.text = this.text.replace(',','.'); // only . are accepted by parseFloat
			
			if(reformat_currency_sign)
			{
				/** remove leading + **/
				if(this.text == '+')
				{
					this.text = '';
				}
				else if(this.text.substring(0,1) == '+')
				{
					this.text = this.text.substring(1,this.text.length);
				}
			}
			
			/** checks if the user wants to
			 * 1. delete period or
			 * 2. enter another period another + or another -
			 * 3. enter a number that is bigger than what flex can compute (and convert) or the mysql database can store (max 99999999.99 min -99999999.99)
			 * which is not allowed, so restore last entered number. **/
			if(detect_if_period_was_deleted() || (count_ocurrency(this.text,'.') > 1) || (count_ocurrency(this.text,'-') > 1) || (count_ocurrency(this.text,'+') > 1) || (parseFloat(this.text) > 99999999.99) || (parseFloat(this.text) < -99999999.99))
			{
				restore_old_value();
			}
			
			/** problem: parse float rundet zahlen wie 999.99 auf 1000
			 * dies ist absolut tödlich maximum of type number 999.999.999.999.999, 999 billionen
			 * olli hat auch für datenbank seite (zB tabelle buchungen -> BruttoBetrag einen decimal(10,2) vereinbart, d.h. maximalwert: 99999999.99
			 * **/
			reformat_to_precision();
			
			if(rounding)
			{
				this.text = c_MATH_inst.TRICK17_ROUNDING(parseFloat(this.text),this.precision,'string');
			}
		}
		
		/** count how often CHAR appears in MATERIAL
		 * returns -1 if char was not found in material.
		 * **/
		public function count_ocurrency(MATERIAL:String,CHAR:String):int
		{
			var occurrencies:int = -1;
			var Char_Int:int = MATERIAL.length;
			for(var counter:int = 0;!(counter == Char_Int);counter++)
			{
				if(CHAR == MATERIAL.charAt(counter))
				{
					if(occurrencies == -1)
					{
						occurrencies = 0;
					}
					
					occurrencies = occurrencies + 1;
				}
			}
			
			return occurrencies;
		}
		
		/** check if the current entered text, is the same as the old text, but without the period **/
		private function detect_if_period_was_deleted():Boolean
		{
			var RESULT_BOOLEAN:Boolean = false;
			if(last_entered_currency != '')
			{
				if(this.text == last_entered_currency.replace('.',''))
				{
					RESULT_BOOLEAN = true;
				}
			}
			var RESULT:Object = find_char(this.text,['.']);
			if(RESULT['CHAR_FOUND'])
			{
				last_entered_currency = this.text;
			}
			
			return RESULT_BOOLEAN;
		}
		
		/** prevents that user deletes the . in currency **/
		private function restore_old_value():void
		{
			this.text = last_entered_currency;
		}
		
		/** checks if the content is a valid int value in format 123
		 * **/
		public function validate_int():Boolean
		{
			valid = true;
			this.text = parseInt(this.text).toString();
			if(this.text == 'NaN')
			{
				valid = false;
				this.behave();
			}
			
			if(this.digits != 'undefined')
			{
				var NUMBER_OF_DIGITS:Number = parseFloat(this.digits);
				if(this.text.length > NUMBER_OF_DIGITS)
				{
					valid = false;
					this.behave();
				}
			}
			
			if(valid)
			{
				this.behave();
			}
			return valid;
		}
		
		/** reformat to precision
		 * simply cut off everything that is too much.
		 * 
		 * allowes on CHANGE not to enter more digits than this.precision.
		 * 
		 * if 0.00 is entered the field will be blank
		 * **/
		public function reformat_to_precision():void
		{
			if(isNaN(parseFloat(this.text)) == false)
			{
				this.text = this.text.replace(',','.'); // only . are accepted by parseFloat
				this.text = this.text.replace('..','.'); // only . are accepted by parseFloat
				
				/** remove leading 0 **/
				var INT_Chars:int = this.text.length;
				
				if(INT_Chars > 1) // bei 0 noch nicht, bei 0XX schon auch bei 0.
				{
					for(var counter:int = 0;!(counter == INT_Chars);counter++)
					{
						var STRING_FirstChar:String = this.text.substr(0,1);
						var STRING_FirstTwoChar:String = getFirstXChars(this.text,2);
						
						if(STRING_FirstChar == '0' && STRING_FirstTwoChar != '0.')
						{
							this.text = STRING_remove_first_char(this.text);
						}
					}
				}
				
				/** add missing trailing 0 **/
				var ARRAY:Array = this.text.split('.');
				var string_before_dot:String = ARRAY[0];
				var string_after_dot:String = ARRAY[1];
				if(string_after_dot == null)
				{
					string_after_dot = '';
				}
				if(string_before_dot == null)
				{
					string_before_dot = '0';
				}
				while(string_after_dot.length < precision)
				{
					string_after_dot = string_after_dot + '0';
				}
				
				/** cut off everything beyond precision **/
				if(string_after_dot.length > precision)
				{
					string_after_dot = string_after_dot.substr(0,precision);
				}
				
				this.text = string_before_dot+'.'+string_after_dot;
			}
		}
		
		/** checks if the content is a valid float value in format 7650.329515372109 (maximum 12 digits after dot)
		 * **/
		public function validate_float():Boolean
		{
			valid = true;
			this.text = this.text.replace(',','.'); // only . are accepted by parseFloat
			this.precision = 12;
			this.text = parseFloat(this.text).toString();
			if(this.text != 'NaN')
			{
				reformat_to_precision();
				if(rounding)
				{
					this.text = c_MATH_inst.TRICK17_ROUNDING(parseFloat(this.text),this.precision,'string');
				}
				
				valid = true;
			}
			else
			{
				valid = false;
				this.behave();
			}
			
			if(valid)
			{
				this.behave();
			}
			return valid;
		}
		
		/** checks if the content is a valid date value in format DD.MM.YYYY or YYYY-MM-DD
		 * **/
		public function validate_date(FORMAT:String = 'DD.MM.YYYY'):Boolean
		{
			this.text = autoComplete_Date(this.text);
			
			valid = validateDate(this.text,FORMAT);
			
			this.behave();
			return valid;
		}
		
		/** validates per regular expression if only aphanumeric characters are used
		 * if one finds regular expressions to be some kind of complicated and unreliable,
		 * one can always try this alternative:
		 * <assets:TIE id="TIE_inst_abc_and_numbers" text="abc ABZ 0123" mode="abc" behaviour_on_valid="{['borderColor green']}" behaviour_on_invalid="{['borderColor red','delete content']}" valid_chars="{['german alphabet','numbers']}"/>
		 * where 'german alphabet','numbers' are keywords for arrays that hold valid values for chars. (can be extended / replaced with custom arrays with valid values) 
		 *  **/
		private function validate_alphanumeric():Boolean
		{
			this.valid_chars = ['german alphabet','numbers'];
			this.validate_abc();
			this.behave();
			return this.valid;
		}
		
		/** checks if the content does not contain certain chars
		 * if given, will first filter out invalid chars, then delete all chars that are not valid. 
		 * **/
		private var c_STRINGS_AND_CONSTANTS_inst:c_STRINGS_AND_CONSTANTS = new c_STRINGS_AND_CONSTANTS();
		
		/** all chars that are valid per default it's the german alphabet_german **/
		public var valid_chars:Array = null;
		
		/** all chars that are not valid **/
		public var invalid_chars:Array = null
		
		public function validate_abc():Boolean
		{
			valid = true;
			var valid_chars_temp:Array = new Array();
			if(valid_chars == null)
			{
				/** default charset is german alphabet **/
				// valid_chars_temp = CONCAT_B(c_STRINGS_AND_CONSTANTS_inst.alphabet_german,valid_chars_temp);
				// valid_chars_temp.push(' ');
				valid_chars_temp = null;
			}
			else
			{
				for each(var CHAR:String in valid_chars)
				{
					if(CHAR == 'german alphabet')
					{
						valid_chars_temp = CONCAT_B(c_STRINGS_AND_CONSTANTS_inst.alphabet_german,valid_chars_temp);
						valid_chars_temp.push(' ');
					}
					else if(CHAR == 'numbers')
					{
						valid_chars_temp = CONCAT_B(c_STRINGS_AND_CONSTANTS_inst.numbers_strings,valid_chars_temp);
						valid_chars_temp.push(' ');
					}
					else
					{
						valid_chars_temp.push(CHAR);
					}
				}
			}
			
			valid_chars = valid_chars_temp;
			
			var start:int = 0;
			var end:int = 1;
			var substring:String = '';
			var char_valid:Boolean = true;
			var RESULT_valid_chars:Object = new Object();
			var RESULT_invalid_chars:Object = new Object();
			for(var counter:int = 0;!(counter == this.length);counter++)
			{
				start = counter;
				end = counter+1;
				substring = this.text.substring(start,end);
				
				if(invalid_chars != null && valid_chars == null)
				{
					RESULT_invalid_chars = find_char(substring,invalid_chars);
					
					if(RESULT_invalid_chars['CHAR_FOUND'] == false)
					{
						char_valid = true;
					}
					else
					{
						char_valid = false;
						break;
					}
				}
				else if(invalid_chars == null && valid_chars != null)
				{
					RESULT_valid_chars = find_char(substring,valid_chars);
					
					if(RESULT_valid_chars['CHAR_FOUND'])
					{
						char_valid = true;
					}
					else
					{
						char_valid = false;
						break;
					}
				}
				else if(invalid_chars != null && valid_chars != null)
				{
					RESULT_invalid_chars = find_char(substring,invalid_chars);
					RESULT_valid_chars = find_char(substring,valid_chars);
					
					// trace(RESULT_invalid_chars['CHAR_FOUND']);
					// trace(RESULT_valid_chars['CHAR_FOUND']);
					if((RESULT_invalid_chars['CHAR_FOUND'] == false) && RESULT_valid_chars['CHAR_FOUND'])
					{
						char_valid = true;
					}
					else
					{
						char_valid = false;
						break;
					}
				}
			}
			
			valid = char_valid;
			if(valid)
			{
				this.behave();
			}
			else
			{
				if(RESULT_invalid_chars['CHAR_FOUND'])
				{
					this.behave(['tooltip: Ungültiges Zeichen \"'+RESULT_invalid_chars['CHAR']+'\" an Stelle: '+counter]);
				}
				if(RESULT_valid_chars['CHAR_FOUND'] == false)
				{
					this.behave(['tooltip: Zeichen \"'+RESULT_valid_chars['CHAR']+'\" an Stelle: '+counter+' wurde im gültigen Zeichensatz nicht gefunden.']);
				}
			}
			
			return valid;
		}

		/** only _valid_values are valid **/
		public var _valid_values:Array = null;

		public function set valid_values(valid_values_var:Array):void
		{
			if(c_OBJECT_inst.not_empty(valid_values_var))
			{
				this._valid_values = valid_values_var;
			}
			this._mode = 'values';
		}
		public function get valid_values():Array
		{
			return this._valid_values;
		}
		
		/** only invalid_values are valid (if this is not important leave it null) **/
		public var invalid_values:Array = null;
		
		public var c_OBJECT_inst:c_OBJECT = new c_OBJECT();
		public var c_STRING_inst:c_STRING = new c_STRING();
		public var c_ARRAY_inst:c_ARRAY = new c_ARRAY();
		public var c_VALIDATORS_inst:c_VALIDATORS = new c_VALIDATORS();
		
		/** if valid_values_property is empty, it is assumed that valid_values is a array of int-indexed strings **/
		public function validate_values():Boolean
		{
			valid = false; // prove it to be a valid value
			if(_valid_values != null)
			{
				for each(var ELEMENT:* in _valid_values)
				{
					if(ELEMENT is String)
					{
						if(this.text == ELEMENT)
						{
							valid = true;
							break;
						}
					}
					else if(ELEMENT is Object)
					{
						if(this.text == ELEMENT[valid_values_property])
						{
							valid = true;
							break;
						}
					}
				}
			}
			// prove it wrong
			if(invalid_values != null)
			{
				for each(var ELEMENT:* in invalid_values)
				{
					if(c_OBJECT_inst.empty(valid_values_property))
					{
						if(this.text == ELEMENT)
						{
							valid = false;
							break;
						}
					}
					else
					{
						if(this.text == ELEMENT[valid_values_property])
						{
							valid = false;
							break;
						}
					}
				}
			}

			this.behave();
			
			return valid;
		}

		/** checks if char is existing in charset array
		 * returns true if char is existing 
		 * **/
		private function find_char(CHAR_TO_TEST:String,CHARSET:Array = null):Object
		{
			var RESULT:Object = {CHAR_FOUND:false,POS:-1,CHAR:''};
			if(CHARSET != null && CHAR_TO_TEST != '' && CHAR_TO_TEST != null)
			{
				RESULT['CHAR_FOUND'] = false;
				for(var counter:int = 0;!(counter == CHARSET.length);counter++)
				{
					if(CHAR_TO_TEST == CHARSET[counter])
					{
						RESULT['CHAR_FOUND'] = true;
						RESULT['POS'] = counter;
						RESULT['CHAR'] = CHAR_TO_TEST;
						break;
					}
				}
			}
			
			return RESULT;
		}
		
		/** checks if the content has a valid length of chars
		 * **/
		public function validate_string_length():Boolean
		{
			valid = true;
			
			if(this.max_length != 'undefined')
			{
				var max_length_number:Number = parseFloat(this.max_length);
				if(!isNaN(max_length_number))
				{
					if(this.text.length > max_length_number)
					{
						valid = false;
						this.behave(['limit content to max_length']);
					}
				}
			}
			if(this.min_length != 'undefined')
			{
				var min_length_number:Number = parseFloat(this.min_length);
				if(!isNaN(min_length_number))
				{
					if(this.text.length < min_length_number)
					{
						valid = false;
						this.behave(['delete content']);
					}
				}
			}
			
			if(valid)
			{
				this.behave();
			}
			
			return valid;
		}
		
		/** possible values:
		 * CHANGE, FOCUS_OUT, FOCUS_IN
		 * currency and percent is automatically validated on CHANGE (as the user types he is not allowed to enter characters that do not match curreny/percent)
		 * **/
		public var validate_on:String = 'FOCUS_OUT'; 
		public function FOCUS_OUT(event:Event = null):void
		{
			if(this.validate_on == 'FOCUS_OUT')
			{
				this.validate(); // -> this.behave();
			}

			if(_mode == 'currency')
			{
				if(isNaN(parseFloat(this.text)))
				{
					valid = false;
				}
				else
				{
					valid = true;
				}
				this.behave();
			}

			if(_autocomplete)
			{
				ShowAutoCompleteDropDown(new FocusEvent("FOCUS_OUT")); // new ToolTipEvent("TOOL_TIP_END")
			}
		}
		
		/** validate currency **/
		public function validate_currency(event:Event = null):Boolean
		{
			reformat_currency();
			
			if(validateCurrency(this.text) || this.text == '-')
			{
				valid = true;
			}
			else
			{
				valid = false;
			}
			
			this.behave();
			
			if(valid)
			{
				last_entered_currency = this.text;
			}
			
			/** select the complete text for immediate overwrite by user **/
			if(this.text == '0.00')
			{
				this.setSelection(0,1);
			}
			
			return valid;
		}
		
		/** validate percent
		 * only positive values are allowed from 0.00 to 100.00 **/
		public function validate_percent(event:Event = null):Boolean
		{
			reformat_currency();
			
			if(validateCurrency(this.text))
			{
				valid = true;
			}
			else
			{
				valid = false;
			}
			
			this.behave();
			
			if(valid)
			{
				last_entered_currency = this.text;
			}
			
			/** select the complete text for immediate overwrite by user **/
			if(this.text == '0.00')
			{
				this.setSelection(0,1);
			}
			
			if(valid)
			{
				this.max_value = '100';
				this.min_value = '0';
				validate_max_min_numeric_value();
				reformat_currency();
			}
			
			return valid;
		}
		
		/** define what kind of value may be entered into the TextInput
		 * 'everything' empty array means = everything allowed
		 * 'currency' -> only numeric values with a precision of 2 are allowed. (otherwise empty textinput)
		 * 'int' -> only full numbers values with a precision of 0 are allowed. (otherwise empty textinput)
		 * 'abc' -> only non numeric text is allowed.
		 * 'date' -> only Date's are allowed. Format DD.MM.YYYY
		 * 'float' -> only Date's are allowed. Format DD.MM.YYYY
		 * 'alphanumeric' ->  only alphanumeric Values allowed
		 * 'values' -> only the given individual values (as array to TIE._valid_values) are allowed/valid values
		 *  **/
		public var _mode:String = 'everything';

		/** list of available validation modes **/
		public var modes:Array = [
		{value:'everything',description:'empty array means = everything allowed'},
		{value:'currency',description:'only numeric values with a precision of 2 are allowed. (otherwise empty textinput)'},
		{value:'int',description:'only full numbers values with a precision of 0 are allowed. (otherwise empty textinput)'},
		{value:'abc',description:'only non numeric text is allowed.'},
		{value:'date',description:'only Dates are allowed. Format DD.MM.YYYY'},
		{value:'float',description:'only Dates are allowed. Format DD.MM.YYYY'},
		{value:'alphanumeric',description:'only alphanumeric Values allowed'},
		{value:'values',description:'only the given individual values (as array to TIE._valid_values) are allowed/valid values'},
		{value:'percent',description:'Percentage minimum: 0.00 maximum 100.00'}
		];

		public function set mode(mode_var:String):void
		{
			if(c_OBJECT_inst.empty(mode_var))
			{
				mode_var = 'everything';
			}
			if(mode_var == "anything")
			{
				mode_var = 'everything';
			}
			if(c_ARRAY_inst.FIND_OBJECT_IN_ARRAY(modes,'value',mode_var) != null)
			{
				this._mode = mode_var;
			}
			else
			{
				throw new Error(" TextInputExtended: "+mode_var+" is an unsupported mode.");
			}
		}
		public function get mode():String
		{
			return this._mode;
		}
		
		/** if a numeric value is allowed, what precision (number of digits allowed after dot) is allowed? **/
		public var precision:int = 2;
		
		/** maximum number of digits allowed before dot **/
		public var digits:String = 'undefined';
		
		/** maximum number of chars allowed **/
		public var max_length:String = 'undefined';
		
		/** minimum number of chars allowed **/
		public var min_length:String = 'undefined';
		
		/** maximum value for numbers or ints **/
		public var max_value:String = 'undefined';
		
		/** minimum value for numbers or ints **/
		public var min_value:String = 'undefined';
		
		/** if the whole text in the textInput should be selected on focusIn **/
		public var select_all_text_on_focusIn:Boolean = true;
		
		/**
		 * v1.0
		 * 1. reformatiert ein unvollständig eingegebenes datum mit dem AKTUELLEM (SYSTEM) datum in das Format DD.MM.YYYY
		 * 2. checkt ob dd > 21 < 0, m > 12 < 0 etc. 
		 * ählich des Flex eingebauten DateValidators, nur dass dieser nicht zuverlässig validiert.
		 * user muss fähig sein Datum in folgenden formaten eingeben zu können
		 * 01.01.2008
		 * 01012008
		 * 0101
		 * 01
		 * '' -> aktuelles datum, aus aktuellem tag + perioden-monat + buchungsjahr.
		 * STRING_Date.replace('.','');
		 * @return An array of strings consisting of all substrings in 
		 * the string that match the specified <code>pattern</code>.
		 * INPUT
		 * MAX_MM -> der maximale gültige Monat.
		 * MIN_MM -> der minmale gültige Monat.
		 **/
		public function autoComplete_Date(STRING_Date:String,MAX_MM:int = 12,MIN_MM:int = 1,MAX_YYYY:int = 9999,MIN_YYYY:int = 0):String
		{
			// 0. validieren ob ein gültiges format forliegt
			// 1. punkte entfernen, falls vorhanden
			
			var STRING_DateResult:String = '';
			
			// replace funktion wo bis zu 3 punkte herausfiltert
			STRING_Date = STRING_Date.replace('.','');
			STRING_Date = STRING_Date.replace('.','');
			STRING_Date = STRING_Date.replace('.','');
			
			if(validateInt(STRING_Date) == false)
			{
				// no valid date
				// -> leave old date
				// STRING_DateResult = STRING_Date;
				// -> delete old date
				return '';
			}
			
			// Check if valid Int
			function validateInt(InputString:String):Boolean // Checkt ob es sich beim Input um ein gültiges Kontonummern-Format handelt d.h. eine ganz-zahlige Nummer ohne Vorzeichen. If input is a number -> true else -> false
			{
				const RegExInt:RegExp = /^\d+$/g; // matches only int numbers
				
				if (RegExInt.test(InputString)) // test returns true if match, else false //// BE CAREFUL! .test proved to do true, false, true... validation errors.
				{
					return true;
				}
				else
				{
					return false;
				}
			}
			
			var dd:String = '';
			var mm:String = '';
			var yyyy:String = '';
			
			var datum:Date = new Date(); // aktuelles datum
			
			// 2. schaun wie viel man hat
			if(STRING_Date.length == 1)
			{
				// wir ham 1
				dd = STRING_Date.substr(0,1); // ab char 0, copy 1 stück
				
				// dd max 31 min 1
				if(parseInt(dd) > 31)
				{
					dd = '31';
				}
				if(parseInt(dd) < 1)
				{
					dd = '1';
				}
				
				dd = '0'+dd;
				mm = (datum.month+1).toString();
				
				// mm max 12 min 1
				if(parseInt(mm) > 12)
				{
					mm = '12';
				}
				if(parseInt(mm) < 1)
				{
					mm = '1';
				}
				
				if(mm.length == 1)
				{
					mm = '0'+mm;
				}
				yyyy = datum.fullYear.toString();
			}
			
			if(STRING_Date.length == 2)
			{
				// wir ham 01
				dd = STRING_Date.substr(0,2); // ab char 0, copy 2 stück
				
				// dd max 31 min 1
				if(parseInt(dd) > 31)
				{
					dd = '31';
				}
				if(parseInt(dd) < 1)
				{
					dd = '1';
				}
				
				mm = (datum.month+1).toString();
				
				// mm max 12 min 1
				if(parseInt(mm) > 12)
				{
					mm = '12';
				}
				if(parseInt(mm) < 1)
				{
					mm = '1';
				}
				
				if(mm.length == 1)
				{
					mm = '0'+mm;
				}
				yyyy = datum.fullYear.toString();
			}
			if(STRING_Date.length == 3)
			{
				// wir ham 011
				dd = STRING_Date.substr(0,2); // ab char 0, copy 2 stück
				
				// dd max 31 min 1
				if(parseInt(dd) > 31)
				{
					dd = '31';
				}
				if(parseInt(dd) < 1)
				{
					dd = '1';
				}
				
				mm = STRING_Date.substr(2,1); // ab char 2, copy 2 stück
				
				// mm max 12 min 1
				if(parseInt(mm) > 12)
				{
					mm = '12';
				}
				if(parseInt(mm) < 1)
				{
					mm = '1';
				}
				
				if(mm.length == 1)
				{
					mm = '0'+mm;
				}
				yyyy = datum.fullYear.toString();
			} 
			if(STRING_Date.length == 4)
			{
				// wir ham 0101
				dd = STRING_Date.substr(0,2); // ab char 0, copy 2 stück
				
				// dd max 31 min 1
				if(parseInt(dd) > 31)
				{
					dd = '31';
				}
				if(parseInt(dd) < 1)
				{
					dd = '1';
				}
				
				mm = STRING_Date.substr(2,2); // ab char 2, copy 2 stück
				
				// mm max 12 min 1
				if(parseInt(mm) > 12)
				{
					mm = '12';
				}
				if(parseInt(mm) < 1)
				{
					mm = '1';
				}
				
				yyyy = datum.fullYear.toString();
			} 
			if(STRING_Date.length == 6)
			{
				// wir ham 010108
				dd = STRING_Date.substr(0,2); // ab char 0, copy 2 stück
				
				// dd max 31 min 1
				if(parseInt(dd) > 31)
				{
					dd = '31';
				}
				if(parseInt(dd) < 1)
				{
					dd = '1';
				}
				
				mm = STRING_Date.substr(2,2); // ab char 2, copy 2 stück
				
				// mm max 12 min 1
				if(parseInt(mm) > 12)
				{
					mm = '12';
				}
				if(parseInt(mm) < 1)
				{
					mm = '1';
				}
				
				yyyy = STRING_Date.substr(4,2); // ab char 4, copy 2 stück
				if(yyyy.length == 2)
				{
					yyyy = '20'+yyyy;
				}
			} 
			if(STRING_Date.length == 8)
			{
				// wir ham 01012008
				dd = STRING_Date.substr(0,2); // ab char 0, copy 2 stück
				
				// dd max 31 min 1
				if(parseInt(dd) > 31)
				{
					dd = '31';
				}
				if(parseInt(dd) < 1)
				{
					dd = '1';
				}
				
				mm = STRING_Date.substr(2,2); // ab char 2, copy 2 stück
				
				// mm max 12 min 1
				if(parseInt(mm) > 12)
				{
					mm = '12';
				}
				if(parseInt(mm) < 1)
				{
					mm = '1';
				}
				
				yyyy = STRING_Date.substr(4,4); // ab char 4, copy 4 stück
			}
			
			if(parseFloat(mm) > MAX_MM)
			{
				mm = MAX_MM.toString();
			}
			
			if(parseFloat(mm) < MIN_MM)
			{
				mm = MIN_MM.toString();
			}
			
			if(parseFloat(yyyy) > MAX_YYYY)
			{
				yyyy = MAX_YYYY.toString();
			}
			
			if(parseFloat(yyyy) < MIN_YYYY)
			{
				yyyy = MIN_YYYY.toString();
			}
			
			STRING_DateResult = dd+'.'+mm+'.'+yyyy;
			
			if(STRING_DateResult == '..')
			{
				// von .length hat nix gepasst. 
				STRING_DateResult = STRING_Date;
			}
			
			
			return STRING_DateResult;
		}
		
		public function CHANGE(event:Event = null):void
		{
			if(_mode == 'currency' || _mode == 'percent')
			{
				this.validate_on = 'CHANGE';
			}
			if(this.validate_on == 'CHANGE')
			{
				this.validate();
			}

			this.changed = true;

			if(_autocomplete)
			{
				autoCompleteInput(event);
			}
		}
		
		public var dataProvider_filtered:Array = null;
		public var dataProvider_original:Array = null;

		/** how to filter the list of possible values
		 * possible values:
		 * -> "match beginning" -> will check if the beginning chars of _valid_values will match
		 * -> "match end" -> will check if the end of any of the _valid_values will match
		 * -> "anywhere" -> will check if user-input is inside any of the _valid_values
		 * **/
		public var autocomplete_mode:String = "match beginning";
		
		/** tries to find a value in dataProvider that matches input, and "looks ahead"
		 * at the same time the dataProvider gets filtered, displaying only relevant information.
		 * **/
		public function autoCompleteInput(event:Event = null):void
		{
			if(c_OBJECT_inst.empty(dataProvider_original))
			{
				dataProvider_original = c_OBJECT_inst.copy(ACDDL.dataProvider);
			}
			if(autocomplete_mode == "match beginning")
			{
				ACDDL.dataProvider = dataProvider_filtered = c_ARRAY_inst.MATCH_BEGINNING_MULTIPLE_KEYS(dataProvider_original,autocompleteKeys,this.text);
			}
			else if(autocomplete_mode == "match end")
			{
				ACDDL.dataProvider = dataProvider_filtered = c_ARRAY_inst.MATCH_END_MULTIPLEKEYS(dataProvider_original,autocompleteKeys,this.text);
			}
			else if(autocomplete_mode == "anywhere")
			{
				ACDDL.dataProvider = dataProvider_filtered = c_ARRAY_inst.MATCH_ANYWHERE_MULTIPLEKEYS(dataProvider_original,autocompleteKeys,this.text);
			}
			
			
			if(_lookAhead)
			{
				if(ACDDL.dataProvider.length == 1)
				{
					LookAheadFunction(ACDDL.dataProvider[0][valid_values_property]);
				}
			}
		}
		
		public var TextSelection:Object = {start:0,end:0};
		public function LookAheadFunction(Wanted:String):void
		{
			if((this.text != "") && (Wanted != null))
			{
				var substr:String = Wanted.substring(0,this.text.length);
				
				if(substr == this.text)
				{
					var oldLength:int = this.text.length;
					this.text = this.text + Wanted.substring(this.text.length,Wanted.length);
					// select the autoCompleted part, to be easily overwritten by user
					this.setSelection(oldLength,Wanted.length);
					TextSelection["start"] = oldLength;
					TextSelection["end"] = Wanted.length; 
				}
			}
		}

		private var TIMER:Timer = null;
		public function disableLookAheadFor(event:Event = null,seconds:int = 1):void
		{
			if(event == null)
			{
				TIMER = new Timer(1000,seconds);
				TIMER.addEventListener(TimerEvent.TIMER_COMPLETE, disableLookAheadFor);
				TIMER.start();
				lookAhead = false;
			}
			else
			{
				lookAhead = true;
				TIMER.stop();
			}
		}
		
		/** if not only tab should change the input-focus to the next component, but if enter should do the same **/
		public var focusNextOnEnter:Boolean = true;

		override protected function keyDownHandler(event:KeyboardEvent):void
		{
			super.keyDownHandler(event);

			this.manually_changed = true;

			if(!event.ctrlKey)
			{
				//An UP "keydown" event on the top-most item in the drop-down
				//or an ESCAPE "keydown" event should change the text in text
				// field to original text
				if(event.keyCode == Keyboard.UP)
				{
					ACDDL.setSelection("UP");
				}
				else if(event.keyCode == Keyboard.DOWN)
				{
					ACDDL.setSelection("DOWN");
				}
				else if(event.keyCode==Keyboard.ESCAPE)
				{
				}
				else if((event.keyCode==Keyboard.BACKSPACE) || (event.keyCode==Keyboard.DELETE))
				{
					disableLookAheadFor(null,1);
				}
				else if(event.keyCode == Keyboard.ENTER)
				{
					if(focusNextOnEnter)
					{
						if(event.shiftKey)
						{
							FocusPreviousComponent();
						}
						else
						{
							FocusNextComponent();
							// if(keepLocalHistory)
							// addToLocalHistory(); // everything that is not in dataprovider
						}
					}
				}
			}
		}

		public var nextComponent:IFocusManagerComponent;
		public function FocusNextComponent():void
		{
			// parentApplication.focusManager
			nextComponent = this.focusManager.getNextFocusManagerComponent();
			this.focusManager.setFocus( nextComponent );
		}
		public var previousComponent:IFocusManagerComponent;
		public function FocusPreviousComponent():void
		{
			// parentApplication.focusManager
			previousComponent = this.focusManager.getNextFocusManagerComponent(true);
			this.focusManager.setFocus( previousComponent );
		}

		/** THE CORE FUNCTION OF THE TIE:
		 * checks if this TIE has valid input according to it's definition, returns true or false
		 * MODE: -> 'NOT EMPTY'
		 * determines what valid means.
		 * is also executed on focus out.
		 * there are currently 8 modes:
		 * 
		 *  **/
		public var valid:Boolean = undefined; 
		public function validate(MODE:String = 'NOT EMPTY'):Boolean
		{
			valid = true;
			if(MODE == 'NOT EMPTY' && (this.text == null || this.text == ''))
			{
				valid = false;
			}
			else
			{
				/** check type **/
				if(this._mode == 'percent')
				{
					validate_percent();
				}
				if(this._mode == 'currency')
				{
					validate_currency();
					if(valid)
					{
						// validate_max_min_numeric_value(); // validate_currency has it's own maximum
					}
				}
				else if(this._mode == 'int')
				{
					validate_int();
					if(valid)
					{
						/** check max min value **/
						validate_max_min_numeric_value();
					}
				}
				else if(this._mode == 'float')
				{
					validate_float();
					if(valid)
					{
						/** check max min value **/
						this.validate_max_min_numeric_value();
					}
				}
				else if(this._mode == 'date')
				{
					this.validate_date();
				}
				else if(this._mode == 'abc')
				{
					this.validate_abc();
				}
				else if(this._mode == 'alphanumeric')
				{
					this.validate_alphanumeric();
				}
				else if(this._mode == 'values')
				{
					this.validate_values();
				}
				
				/** check max min length **/
				if(valid)
				{
					validate_string_length();
				}
			}
			
			
			return valid;
		}
		
		/** what actions the TIE should perform when being validated invalid **/
		public var behaviour_on_invalid:Array = ['delete content'];
		/** what actions the TIE should perform when being validated valid **/
		public var behaviour_on_valid:Array = [];
		
		/** will be Message of the Alert.show box if TIE validates with result false and behaviour_on_invalid="{['show alert']}" **/
		public var AlertMessageOnInvalid:String = "";
		/** will be Title of the Alert.show box if TIE validates with result false and behaviour_on_invalid="{['show alert']}" **/
		public var AlertTitleOnInvalid:String = "Eingabe ungültig kann nicht fortfahren.";
		
		/** will be Message of the Alert.show box if TIE validates with result false and behaviour_on_valid="{['show alert']}" **/
		public var AlertMessageOnValid:String = "";
		/** will be Title of the Alert.show box if TIE validates with result false and behaviour_on_valid="{['show alert']}" **/
		public var AlertTitleOnValid:String = "Eingabe gültig.";

		/** executes BEHAVIOUR (IF_INVALID) array contains certain actions **/
		public function behave(BEHAVIOURS:Array = null,AlertMessageOnInvalid:String = null,AlertTitleOnInvalid:String = null):void
		{
			if(c_OBJECT_inst.empty(AlertMessageOnInvalid))
			{
				AlertMessageOnInvalid = this.AlertMessageOnInvalid;
			}
			if(c_OBJECT_inst.empty(AlertTitleOnInvalid))
			{
				AlertTitleOnInvalid = this.AlertTitleOnInvalid;
			}
			if(c_OBJECT_inst.empty(AlertMessageOnValid))
			{
				AlertMessageOnValid = this.AlertMessageOnValid;
			}
			if(c_OBJECT_inst.empty(AlertTitleOnValid))
			{
				AlertTitleOnValid = this.AlertTitleOnValid;
			}
			if(valid)
			{
				/** behaviours if the TIE is valid **/
				if(BEHAVIOURS == null) { BEHAVIOURS = behaviour_on_valid; }
				for each(var ACTION_ON_VALID:String in BEHAVIOURS)
				{
					if(ACTION_ON_VALID == 'borderColor green')
					{
						this.setStyle('borderColor','green');
					}
				}
			}
			else
			{
				/** behaviours if the TIE is in-valid **/
				if(BEHAVIOURS == null) { BEHAVIOURS = behaviour_on_invalid; }
				
				for each(var ACTION_ON_INVALID:String in BEHAVIOURS)
				{
					if(ACTION_ON_INVALID == 'delete content')
					{
						this.text = '';
					}
					if(ACTION_ON_INVALID == 'show alert')
					{
						if(this.valid)
						{
							Alert.show(AlertMessageOnValid,AlertTitleOnValid);
						}
						else
						{
							Alert.show(AlertMessageOnInvalid,AlertTitleOnInvalid);
						}
					}
					if(ACTION_ON_INVALID == 'set to max numeric value')
					{
						this.text = this.max_value;
					}
					if(ACTION_ON_INVALID == 'set to min numeric value')
					{
						this.text = this.min_value;
					}
					if(ACTION_ON_INVALID == 'limit content to max_length')
					{
						// cut off last entered chars
						if(max_length != 'undefined')
						{
							this.text = this.text.substring(0,parseInt(max_length));
						}
					}
					if(ACTION_ON_INVALID.substring(0,8) == 'tooltip:')
					{
						this.toolTip = ACTION_ON_INVALID.substring(8,ACTION_ON_INVALID.length);
						behave(); // call user defined behaviours
					}
					if(ACTION_ON_INVALID == 'borderColor red')
					{
						this.setStyle('borderColor','red');
					}
				}
			}
		}
		
		/** LIMIT_START:Date = null -> start date... the start-limit, no date smaler/prior this date is allowed. **/
		public var DATE_START_STRING:String = '';
		public var DATE_START:Date = null;
		/** DATE_STOP:Date = null -> stop date... the stop-limit, no date bigger/after this date is allowed. **/
		public var DATE_STOP_STRING:String = '';
		public var DATE_STOP:Date = null;
		
		var RegExDate:RegExp = new RegExp();
		
		/**Check if valid date in format 
		 * currently supported formats:
		 * 'DD.MM.YYYY'
		 * YYYY-MM-DD
		 * **/
		/** INPUT: **/
		/** DATE_START:Date = null -> start date... the start-limit, no date smaler/prior this date is allowed. **/
		/** DATE_STOP:Date = null -> stop date... the stop-limit, no date bigger/after this date is allowed. **/
		public function validateDate(DATE_STRING:String,FORMAT:String = 'DD.MM.YYYY'):Boolean
		{
			if(DATE_START == null)
			{
				if(DATE_START_STRING != '')
				{
					DATE_START = string_to_date(DATE_START_STRING);
				}
			}
			if(DATE_STOP == null)
			{
				if(DATE_STOP_STRING != '')
				{
					DATE_STOP = string_to_date(DATE_STOP_STRING);
				}
			}
			
			var RESULT:Boolean = undefined;
			var DATE:Date = null;
			
			/** format checken **/
			if(FORMAT == 'DD.MM.YYYY')
			{
				RegExDate = new RegExp(/^\d\d\.\d\d\.\d\d\d\d$/g); // matches date in Format 12.12.1912
				if(RegExDate.test(DATE_STRING))
				{
					/** gültigkeit der werte checken, 31.02 is zB. nicht erlaubt. **/
					DATE = DateField.stringToDate(DATE_STRING,'DD.MM.YYYY');
					
					if(DATE != null)
					{
						RESULT = true;
					}
					else
					{
						RESULT = false;
					}
				}
				else
				{
					RESULT = false;
				}
			}
			
			if(FORMAT == 'YYYY-MM-DD')
			{
				RegExDate = new RegExp(/^\d\d\d\d-\d\d\-\d\d\$/g); // matches date in Format 1912-12-12
				if(RegExDate.test(DATE_STRING))
				{
					/** gültigkeit der werte checken, 31.02 is zB. nicht erlaubt. **/
					DATE = DateField.stringToDate(DATE_STRING,'YYYY-MM-DD');
					
					if(DATE != null)
					{
						RESULT = true;
					}
					else
					{
						RESULT = false;
					}
				}
				else
				{
					RESULT = false;
				}
			}
			
			if(DATE != null)
			{
				if(DATE_START != null)
				{
					if(DATE_START.getTime() <= DATE.getTime())
					{
						RESULT = true; 
					}
					else
					{
						RESULT = false; 
						this.toolTip = 'Eigegebenes Datum ist kleines als das zulässige Start-Datum: '+date_to_string(DATE_START);
					}
				}
				
				if(DATE_STOP != null)
				{
					if(DATE.getTime() <= DATE_STOP.getTime())
					{
						RESULT = true; 
					}
					else
					{
						RESULT = false;
						this.toolTip = 'Eigegebenes Datum ist größer als das zulässige End-Datum: '+date_to_string(DATE_STOP);
					}
				}
			}
			
			return RESULT;
		}
		
		/** attaches ArrayA at the end of ArrayB
		 * 
		 * EXAMPLE
		 var ArrayA:Array = [1,2,3,4,5];
		 var ArrayB:Array = [6,7,8,9,10];
		 var ArrayA+ArrayB:Array = CONCAT(ArrayA,ArrayB);
		 * **/
		public function CONCAT_B(ArrayA:Array,ArrayB:Array):Array
		{
			var LENGTH:int = ArrayA.length;
			for(var counter:int = 0;!(counter == LENGTH);counter++)
			{
				ArrayB.push(copy_object(ArrayA[counter]))
			}
			
			return ArrayB;
		}
		
		
		/** erstellt eine unabhängige Kopie vom original Array
		 * FROM: copy all elements starting from index FROM (where 0 is the first element)
		 * TO: copy all elements to index TO (where -1 is the last element)
		 * **/
		/** BENCHMARK: mit optimierter c_OBJECT_inst_copy funktion: 1million in 5.162... **/
		var RESULT_COPY:Array = new Array(); /** das auslagern von variablen deklarationen bringt einen geschwindigkeitszuwachs von 5.744 sec d.h. 24.72% **/
		var OBJECT_COPY:Object = new Object();
		public function copy_array(ARRAY:Array,FROM:int = 0,TO:int = -1):Array
		{
			RESULT_COPY = new Array();
			OBJECT_COPY = new Object();
			if(ARRAY != null)
			{
				if(ARRAY.length > 0) /** das weglassen dieser abfrage verlangsamt sogar die funktion! um c.a. 0.3 sec! **/
				{
					/** var c_OBJECT_inst:c_OBJECT = new c_OBJECT();  // das auslagern von variablen deklarationen bringt einen geschwindigkeitszuwachs von 5.744 sec d.h. 24.72% **/
					if(TO > ARRAY.length)
					{
						TO = ARRAY.length;
					}
					
					if(TO == -1)
					{
						TO = ARRAY.length;
					}
					
					/** copy only range of array **/
					for(var counter:int = FROM;!(counter == TO);counter++) /** lustigerweise bringt es keinen geschwindigkeitsvorteil, wenn man den counter ausserhalb der funktion definiert **/
					{
						this.OBJECT_COPY = ARRAY[counter];
						this.RESULT_COPY.push(copy_object(this.OBJECT_COPY));
					}
				}
			}
			
			return this.RESULT_COPY;
		}
		
		/** erstellt eine unabhängige instanz ders objektes **/
		var STRING_Key:String = '';
		var INDEPENDENT_OBJECT:Object = new Object();
		public function copy_object(OBJECT:Object):Object
		{
			STRING_Key = '';
			INDEPENDENT_OBJECT = new Object();
			for(STRING_Key in OBJECT)
			{
				if(STRING_Key != null && STRING_Key != '' && STRING_Key != undefined)
				{
					this.INDEPENDENT_OBJECT[STRING_Key] = OBJECT[STRING_Key];
				}
			}
			
			/** falls das OBJECT nur aus einem einzelnen string ebsteht **/
			if(OBJECT is String)
			{
				INDEPENDENT_OBJECT = OBJECT;
			}
			
			return this.INDEPENDENT_OBJECT;
		}
		
		
		/** removes the first char of a string
		 * // CHOP FIRST CHAR, ERSTEN BUCHSTABEN LÖSCHEN **/
		public function STRING_remove_first_char(INPUT:String):String
		{
			var STRING_Result:String = '';
			STRING_Result = INPUT.slice(1,INPUT.length);
			
			return STRING_Result;
		}
		
		/** return the first x-amount of chars of a string
		 * gibt die ersten x buchstaben aus. **/
		public function getFirstXChars(INPUT:String,INT_Chars:int):String
		{
			var STRING_Result:String = '';
			if(INT_Chars < 0)
			{
				INT_Chars = 0;
			}
			
			//                return string  from,to
			STRING_Result = INPUT.substr(0,INT_Chars);
			
			return STRING_Result;
		}
		
		// funktion welche das ihr übergebene TextInput.text auf Currency validiert und dann true/false ausgibt.
		public function validateCurrency(INPUT:String):Boolean
		{
			const RegExCurrency_v2:RegExp = /^[-\+]?\d+[\.,]?\d{0,2}$/g; // matches 1 12 123 123.12 +123.12 and -123.12 wobei , or . egal ist. // matches 123.12 +123.12 and -123.12 wobei , or . egal ist.
			// \d{2} matches previous item 2 times
			// \d{0,2} matches previous item 0-2 times
			// ? matches previous 1x or 0x times
			// [abc] groub of possible items (matches a or b or c but not abc)
			// + matches previous 1 or more times
			// Flag g matches globally = more than one time
			// ^ marks beginning
			// $ marks end
			
			if(INPUT.hasOwnProperty('text'))
			{
				INPUT = INPUT['text'];
			}
			
			var TempArray:Array = INPUT.match(RegExCurrency_v2) // returns matching string
			//var TempString:String = .toString();
			
			if(TempArray.length !== 0)
			{
				return true; // bricht nach return ab!!! rest der funktion wird nicht bearbeitet!}
			}
			else
			{
				return false;
			}
			
			// eigentlich solllte es so klappen, aber das liefert nur abwechselnd true/false und keiner weiss warum (closed c++ source sei dank)
			/*  if (RegExTemp.test(InputString)) // test returns true if match, else false
			{
			return true; // bricht nach return ab!!! rest der funktion wird nicht bearbeitet!
			}
			else
			{
			return false;
			}
			*/
		}
		
		public function string_to_date(STRING_Datum:String,STRING_DateFormat:String = 'DD.MM.YYYY'):Date // STRING_DateFormat = 'DD.MM.YYYY'
		{
			var DATUM_result:Date = null;
			if(STRING_Datum != null)
			{
				DATUM_result = DateField.stringToDate(STRING_Datum,STRING_DateFormat);
				/** setz dateUTC nicht korrekt **/
			}
			
			return DATUM_result;
		}
		
		public function date_to_string(DATE_date:Date,FORMAT:String = 'DD.MM.YYYY'):String
		{
			var STRING_result:String = '';
			// Just BTW: Flex assigns 0 to January, 1 to February, 2 to March, ...
			if(DATE_date != null)
			{
				if(FORMAT == 'DD.MM.YYYY')
				{
					// trace(DATE_date.toDateString()); // Fri Sep 12 2008
					
					// trace('UTC:'+DATE_date.dateUTC.toString()+'.'+(DATE_date.monthUTC+1).toString()+'.'+DATE_date.fullYear); // 12.9.2008
					
					/** I PREFER THIS OVER UTC, because when using
					 * 	var DATUM_result:Date = c_DATE_inst.string_to_date(STRING_Datum,STRING_DateFormat);
					 the UTC values are not set correctly. **/
					
					// trace('   :'+DATE_date.date.toString()+'.'+(DATE_date.month+1).toString()+'.'+DATE_date.fullYear); // 12.9.2008
					
					var STRING_month:String = (DATE_date.month+1).toString();
					
					if(STRING_month.length == 1) // ein ziffriger monat hänge ne null hin
					{
						STRING_month = '0'+STRING_month
					}
					var STRING_day:String = DATE_date.date.toString();
					
					if(STRING_day.length == 1) // ein ziffriger monat hänge ne null hin
					{
						STRING_day = '0'+STRING_day
					}
					
					STRING_result = STRING_day+'.'+STRING_month+'.'+DATE_date.fullYear;
					// trace(STRING_result); // 12.09.2008
				}
				
				if(FORMAT == 'YYYY-MM-DD')
				{
					var STRING_month:String = (DATE_date.month+1).toString();
					if(STRING_month.length == 1) // ein ziffriger monat hänge ne null hin
					{
						STRING_month = '0'+STRING_month
					}
					var STRING_day:String = DATE_date.date.toString();
					if(STRING_day.length == 1) // ein ziffriger monat hänge ne null hin
					{
						STRING_day = '0'+STRING_day
					}
					STRING_result = DATE_date.fullYear+'-'+STRING_month+'-'+STRING_day;
					// trace(STRING_result); // 12.09.2008
				}
			}
			
			return STRING_result;
		}

		public var ACDDL:AutoCompleteDropDown = new AutoCompleteDropDown();
		public var TOOLTIP:ToolTip = null;
		
		public function get dataProvider():Array { 
			return ACDDL.dataProvider;
		}

		public function set dataProvider(values:Array):void 
		{
			if(ACDDL == null)
			{
				ACDDL = new AutoCompleteDropDown(); 
			}
			ACDDL.dataProvider = values;
		}
		
		/** all keys that should not dissapear in the autocomplete DropDown **/
		private var _autocompleteHideKeys:Array = new Array();

		public function get autocompleteHideKeys():Array { 
			return ACDDL.autocompleteHideKeys;
		}

		public function set autocompleteHideKeys(values:Array):void 
		{
			if(ACDDL == null)
			{
				ACDDL = new AutoCompleteDropDown(); 
			}
			ACDDL.autocompleteHideKeys = values;
		}

		/** set the width of the autocomplete popup, either as percent 100% or as pixelbased number e.g. 150 **/
		private var _autoCompleteWidth:String = "";
		public function set autoCompleteWidth(autoCompleteWidth_var:String):void
		{
			this._autoCompleteWidth = autoCompleteWidth_var;
		}
		public function get autoCompleteWidth():String
		{
			return this._autoCompleteWidth;
		}

		/** set the Height of the autocomplete popup, either as percent 100% or as pixelbased number e.g. 150 **/
		private var _autoCompleteHeight:String = "";
		public function set autoCompleteHeight(autoCompleteHeight_var:String):void
		{
			this._autoCompleteHeight = autoCompleteHeight_var;
		}
		public function get autoCompleteHeight():String
		{
			return this._autoCompleteHeight;
		}

		/** create & destroy tooltips **/
		private function ShowAutoCompleteDropDown(event:Event = null):void
		{
			if(event.type == "FOCUS_IN")
			{
				/**
				 *  Adds a child DisplayObject to this Container.
				 *  The child is added at the index specified.
				 *
				 *  <p><b>Note: </b>While the <code>child</code> argument to the method
				 *  is specified as of type DisplayObject, the argument must implement
				 *  the IUIComponent interface to be added as a child of a container.
				 *  All Flex components implement this interface.</p>
				 *
				 *  <p>Children are layered from back to front.
				 *  In other words, if children overlap, the one with index 0
				 *  is farthest to the back, and the one with index
				 *  <code>numChildren - 1</code> is frontmost.
				 *  This means the newly added children are layered
				 *  in front of existing children.</p>
				 *
				 *  <p>When you add a new child at an index that is already occupied
				 *  by an old child, it doesn't replace the old child; instead the
				 *  old child and the ones after it "slide over" and have their index
				 *  incremented by one.
				 *  For example, suppose a Container contains the children
				 *  (A, B, C) and you add D at index 1.
				 *  Then the container will contain (A, D, B, C).
				 *  If you want to replace an old child, you must first remove it
				 *  before adding the new one.</p>
				 *
				 *  @param child The DisplayObject to add as a child of this Container.
				 *  It must implement the IUIComponent interface.
				 *
				 *  @param index The index to add the child at.
				 *
				 *  @return The added child as an object of type DisplayObject. 
				 *  You typically cast the return value to UIComponent, 
				 *  or to the type of the added component.
				 *
				 *  @see mx.core.IUIComponent
				 */

				if(c_OBJECT_inst.not_empty(autoCompleteWidth))
				{
					setWidth(ACDDL,autoCompleteWidth);
				}

				if(c_OBJECT_inst.not_empty(autoCompleteHeight))
				{
					setHeight(ACDDL,autoCompleteHeight);
				}

				var pt:Point = new Point(this.x, this.y);
				pt = this.localToGlobal(pt);
				ACDDL.y = this.height+pt.y;
				// ACDDL.y = this.height+this.y; // not really working
				ACDDL.x = pt.x;
				// ACDDL.x = this.x;
				
				ACDDL.width = this.width;
				
				try
				{
					// _window = TitleWindow(PopUpManager.createPopUp(this, TitleWindow, true)); 		// create & show popup with MODALITY TRUE (meaning nothing else can get focus) 
					// _window.showCloseButton = true;													// change properties of popup
					// _window.addEventListener(CloseEvent.CLOSE, closeHandler_Function_RemovePopup);  //  after creatiing it
					// event.toolTip = ACDDL;
					systemManager.toolTipChildren.addChild(ACDDL);
					// this.parent.stage.addChild(ACDDL);
					// stage.addChild(ACDDL);
					// ACDDL.move(curX,curY);			
				}
				catch(e:Error)
				{
					trace("TextInputExtended: "+e);
				}
			}
			
			if(event.type == "FOCUS_OUT")
			{
				try
				{
					systemManager.toolTipChildren.removeChild(ACDDL);
					// this.parent.stage.removeChild(ACDDL);
				}
				catch(e:Error)
				{
					trace("TextInputExtended: "+e);
				}
			}
		}
		
/*		public function createTip(event:MouseEvent):void
		{
			var pt:Point = new Point(event.currentTarget.x, event.currentTarget.y);
			pt = event.currentTarget.localToGlobal(pt);
			
			var curX:Number = pt.x;
			var curY:Number = pt.y + event.currentTarget.height;
			
			if (curX + ACDDL.width > Application.application.width) {
				curX = Application.application.width - ACDDL.width;
			}
			ACDDL.x = curX;
			
			var sm:ISystemManager = Application.application.systemManager;				
			sm.toolTipChildren.addChild(ACDDL);
			
			ACDDL.move(curX,curY);				
		}
*/		
		public function setWidth(OBJECT:UIComponent,width:String):void
		{
			if(c_OBJECT_inst.empty(width))
			{
				OBJECT.width = this.width;
			}
			else
			{
				if(c_STRING_inst.getLastChar(width) == "%")
				{
					if(c_VALIDATORS_inst.validateNumber(width))
					{
						OBJECT.percentWidth = parseFloat(c_STRING_inst.REMOVE_LAST_CHAR(width));
					}
					else
					{
						throw new Error("setWidth, width value is not numeric.");
					}
				}
				else
				{
					if(c_VALIDATORS_inst.validateNumber(width))
					{
						OBJECT.width = parseFloat(width);
					}
					else
					{
						throw new Error("setWidth, width value is not numeric.");
					}
				}
			}
		}
		public function setHeight(OBJECT:UIComponent,height:String):void
		{
			if(c_OBJECT_inst.empty(height))
			{
				OBJECT.height = this.height;
			}
			else
			{
				if(c_STRING_inst.getLastChar(height) == "%")
				{
					if(c_VALIDATORS_inst.validateNumber(height))
					{
						OBJECT.percentHeight = parseFloat(c_STRING_inst.REMOVE_LAST_CHAR(height));
					}
					else
					{
						throw new Error("setHeight, height value is not numeric.");
					}
				}
				else
				{
					if(c_VALIDATORS_inst.validateNumber(height))
					{
						OBJECT.height = parseFloat(height);
					}
					else
					{
						throw new Error("setHeight, height value is not numeric.");
					}
				}
			}
		}
	}
}